#include "huzhou_common.h"
#include "ros/ros.h"

#include <std_srvs/Empty.h>

#include "llm_node/llm_asr.h"  // srv file
#include "llm_node/llm_tts.h"
#include "llm_node/llm_goBack.h"

#include <string>
#include "std_msgs/String.h"

#include "ServerThread.h"

#include <ros/callback_queue.h>

// rosservice call /llm_asr_srv "{}"
// rosservice call /llm_kws_srv "{}"
//


namespace _tts {
    bool ttsServerCallback(llm_node::llm_tts::Request & request, 
                           llm_node::llm_tts::Response & response) {
        sys_log("tts server call.");
        hzc::tts->appendText(request.data);
        // hzc::tts->notifyInputFinished();
        
        if (request.block) {
            hzc::tts->notifyInputFinished();
            hzc::waitForTtsAudioEmpty();
        }

        return true;
    }
};


namespace _asr {
    bool asrCapture = false;
    std::string capStr;
    
    AsrStream::asrContinueEnum asr_callback(std::string &res)
    {
        hzc::asr->pause(); // 暂停音频数据的输入

        capStr = res;
        asrCapture = true;

        return AsrStream::asrContinue;
    }

    bool asrServerCallback(llm_node::llm_asr::Request & request, 
                           llm_node::llm_asr::Response & response) {
        sys_log("asr server call.");

        asrCapture = false;
        capStr = "";

        // 设置语音识别的回调函数
        hzc::asr->setAsrCallback(asr_callback);

        // 设置为 语音识别 的 callback
        hzc::asr->setInStreamCallbackProcessAudio();
        hzc::asr->resume();

        // wait until capture asr result.
        while (!asrCapture && !hzc::exitFlag && ros::ok()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(hzc::sleepPeriodMs));
        }

        sys_log("capture asr: %s", capStr.c_str());

        // 写入结果
        response.res = capStr;

        // 恢复到原始状态
        hzc::asr->pause();
        hzc::asr->setReadInStreamCallback(nullptr);
        return true;
    }

};


namespace _kws {
    bool keywordDetectFlag = false;

    void OnKeywordDetected(const std::string &keyword)
    {
        sys_log("detect keywords: %s", keyword.c_str());
        keywordDetectFlag = true;
    }

    bool kwsServerCallback(
        std_srvs::Empty::Request &req, 
        std_srvs::Empty::Response &res)
    {
        sys_log("kws server call.");

        keywordDetectFlag = false;

        // 设置为关键词识别
        // 切换到关键词识别的callback
        hzc::asr->setReadInStreamCallback(hzc::asr_inputStream_callback);
        hzc::asr->resume();  // 恢复语音数据输入
        
        hzc::kws->SetKeywordDetectedCallback(OnKeywordDetected);

        while (!keywordDetectFlag && !hzc::exitFlag && ros::ok()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(hzc::sleepPeriodMs));
        }

        // 恢复到原始状态
        hzc::asr->setReadInStreamCallback(nullptr);
        hzc::asr->pause();

        return true;
    }
}


namespace _dialog {
    ros::Publisher req_pub;

    ros::Timer timer;

    constexpr int rosTimerPeriod = 20; // 10s 的时间间隔
    bool timerFlag = false;

    enum class SysStatus
    {
        WAIT_FOR_KWS,       // 正在等待检测关键词
        WAIT_FOR_ASR,       // 正在等待检测到语音输入
        WAIT_FOR_AUDIO_END, // 正在等待大模型结束和语音播放结束
        // WAIT_FOR_RESTART     // 一次对话流程结束，等待下一次对话
    };

    SysStatus sys_status = SysStatus::WAIT_FOR_KWS;

    static ServerThread openAsrThread([]()
    {
        // TTS 处理
        hzc::tts->appendText("你好！我在呢！");
        hzc::tts->notifyInputFinished();

        hzc::waitForTtsAudioEmpty();

        // 规定rebot说完打招呼的语言之后才是等待asr，如果在rebot打招呼的时候打断会产生bug
        sys_status = SysStatus::WAIT_FOR_ASR;

        // 切换到 ASR
        hzc::asr->setInStreamCallbackProcessAudio();
        hzc::asr->resume(); });

    // 回调函数，当检测到关键词时调用，这个函数会在 kws->DetectKeyword 中被调用
    void OnKeywordDetected(const std::string &keyword)
    {
        timer.stop();

        sys_log("pause asr audio stream."); // 暂停音频数据输入和识别
        hzc::asr->pause();
        hzc::tts->setMute(false); // 设置扬声器静音

        // 识别到了关键词
        sys_log("Detected keyword: %s", keyword.c_str());

        // 把后续工作放入线程中处理，保证尽快退出回调函数，防止音频数据积累
        openAsrThread.notify();
    }

    // 语音识别识别到了一些内容，发送给大模型
    AsrStream::asrContinueEnum asr_callback(std::string &res)
    {

        sys_status = SysStatus::WAIT_FOR_AUDIO_END;

        sys_log("%s", res.c_str());

        hzc::asr->pause(); // 暂停音频数据的输入

        // 先发送一些套话，同时等待大模型的回答
        hzc::tts->appendText("我识别到您刚刚说的是，" + res + "。我正在深入分析这个问题。请您稍等片刻。");
        hzc::tts->notifyInputFinished();

        // 将识别到的问题发送给大语言模型
        std_msgs::String msg;
        msg.data = "你是一名在医院陪伴患者的机器人，下面是患者对你说的话:'" + res + "'。";
        req_pub.publish(msg);

        return AsrStream::asrContinue;
    }

    // 等待语音播放完成，恢复 语音输入 和 关键词检测
    static ServerThread resumeKwsThread([]()
                                        {
        sys_log("llm server done, notify input finished.");
        hzc::tts->notifyInputFinished();

        // 等待语音播放完成
        hzc::waitForTtsAudioEmpty();

        // hzc::tts->fillSilence(); // ?

        // 恢复关键词识别
        hzc::asr->setReadInStreamCallback(hzc::asr_inputStream_callback); // 切换到关键词识别的callback
        hzc::asr->resume();  // 恢复语音数据输入

        sys_status = SysStatus::WAIT_FOR_KWS;

        timer.start(); // 先进行 stop 再进行 start 会重置定时器的计时

        sys_log("Bingo! you can say keywords again."); });
                

    // 从 大模型 中接受到了一些内容：
    void llmRecStreamData(const std_msgs::String::ConstPtr &msg_p)
    {

        sys_log("rec: %s", msg_p->data.c_str());

        // 判断是否是结束，如果以 "[DONE]" 开头的话，就说明大模型的回答已经结束了
        if (msg_p->data.size() >= 6 && msg_p->data.compare(0, 6, "[DONE]") == 0)
        {



            // 把后续工作放入一个线程中，尽快退出回调函数，否则音频区会积累音频数据
            resumeKwsThread.notify();
        }
        else
        {
            // 添加到语音合成的 buffer 里面，进行语音的合成
            hzc::tts->appendText(msg_p->data);
        }
    }

    bool dialogServerCallback(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
    {
        sys_log("dialog server callback get!");
        
        // 设置 kws 的回调函数 和 asr 的回调函数
        hzc::kws->SetKeywordDetectedCallback(OnKeywordDetected);
        hzc::asr->setAsrCallback(asr_callback);

        // 切换到关键词识别模型
        hzc::asr->setReadInStreamCallback(hzc::asr_inputStream_callback);
        // 恢复语音输入
        hzc::asr->resume();

        // 启动定时器，检测一定时间没有识别到关键词的话就推出
        timerFlag = false;
        timer.start();
        while (!timerFlag && !hzc::exitFlag)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(hzc::sleepPeriodMs));
        }
        
        // 恢复原有的情况
        hzc::asr->pause();
        hzc::asr->setReadInStreamCallback(nullptr);

        sys_log("dialog server callback exit.");

        return true;
    }

    void timerCallback(const ros::TimerEvent &event)
    {
        // 停止语音输入
        hzc::asr->pause();

        // 停止定时器
        timer.stop();
        timerFlag = true;
    }
}


int main(int argc, char ** argv)
{
    hzc::init();

    ros::init(argc, argv, "llm_core");

    ros::NodeHandle nh;

    // 关键词检测的服务 service 
    ros::ServiceServer service = nh.advertiseService(
        "llm_kws_srv", _kws::kwsServerCallback);

    // 语音识别的服务 service 
    ros::ServiceServer asrServer = nh.advertiseService(
        "llm_asr_srv", _asr::asrServerCallback);

    // 创建一个周期性的不立即启动的定时器
    _dialog::timer = nh.createTimer(ros::Duration(_dialog::rosTimerPeriod), 
                                    _dialog::timerCallback, false, false);


    // ==================================================================================
    // 创建一个新的线程来处理"llm dialog"节点的回调，因为这个回调会阻塞线程   begin >>>>>>>>>>>>>>
    // ==================================================================================
    // 创建自定义队列
    ros::CallbackQueue custom_queue;

    // 将 NodeHandle 绑定到这个队列
    ros::NodeHandle nh_private;
    nh_private.setCallbackQueue(&custom_queue);

    // 自身节点的对话服务 -> 这个回调函数会阻塞线程
    ros::ServiceServer dialog_srv = nh_private.advertiseService(
        "/llm_dialog_srv", _dialog::dialogServerCallback);

    // 用独立线程处理这个队列
    std::thread queue_thread([&custom_queue]() {
        while (ros::ok() && !hzc::exitFlag) {
            custom_queue.callAvailable(ros::WallDuration(0.1));  // 处理这个队列的回调
        }
        sys_log("exit 'custom queue thread'.");
    });
    queue_thread.detach();
    // ==================================================================================
    // 创建一个新的线程来处理"llm dialog"节点的回调，因为这个回调会阻塞线程   end <<<<<<<<<<<<<<<<
    // ==================================================================================



    // ==================================================================================
    // 创建一个新的线程来处理"llm dialog"节点的回调，因为这个回调会阻塞线程   begin >>>>>>>>>>>>>>
    // ==================================================================================
    // 创建自定义队列
    ros::CallbackQueue tts_queue;

    // 将 NodeHandle 绑定到这个队列
    ros::NodeHandle nh_tts;
    nh_tts.setCallbackQueue(&tts_queue);

    // 语音合成的 tts
    ros::ServiceServer tts_srv = nh_tts.advertiseService(
        "/llm_tts_srv", _tts::ttsServerCallback);

    // 用独立线程处理这个队列
    std::thread tts_thread([&tts_queue]() {
        while (ros::ok() && !hzc::exitFlag) {
            tts_queue.callAvailable(ros::WallDuration(0.1));  // 处理这个队列的回调
        }
        sys_log("exit 'custom queue thread'.");
    });
    tts_thread.detach();
    // ==================================================================================
    // 创建一个新的线程来处理"llm dialog"节点的回调，因为这个回调会阻塞线程   end <<<<<<<<<<<<<<<<
    // ==================================================================================




    // ==================================================================================
    // 创建一个新的线程来处理"llm dialog"节点的回调，因为这个回调会阻塞线程   end <<<<<<<<<<<<<<<<
    // ==================================================================================
    // 接受大语言模型的输出
    ros::Subscriber sub = nh.subscribe<std_msgs::String>(
        "huzhou_llm_res", 10, _dialog::llmRecStreamData);

    // 向大语言模型发布语音识别到的问题
    _dialog::req_pub = nh.advertise<std_msgs::String>("huzhou_llm_req", 10);
    // ==================================================================================
    // 创建一个新的线程来处理"llm dialog"节点的回调，因为这个回调会阻塞线程   end <<<<<<<<<<<<<<<<
    // ==================================================================================


    sys_log("llm core init done.");

    ros::spin();

    hzc::signalHandler(0);
}
